/* Copyright (C) 1997-2005, Antony L Hosking.
 * All rights reserved.  */
package PPC;

import java.util.*;
import Assem.Instr;
import Translate.Temp;
import Translate.Temp.*;
import Translate.Tree.*;
import Translate.Tree.Exp.*;
import Translate.Tree.Stm.*;

public class Codegen implements Translate.Tree.Visitor<LinkedList<Instr>, Temp> {
    private Frame frame;

    public Codegen(Frame f) {
        frame = f;
    }

    static Instr MOVE(Temp d, Temp s) {
        return new Instr.MOVE("\tmr `d0,`s0", d, s);
    }

    private static Temp[] T(Temp... a) {
        return a;
    }

    static Instr OPER(String a, Temp[] d, Temp[] s, Label... j) {
        return new Instr.OPER("\t" + a, d, s, j);
    }

    private static boolean CONST16(Exp e) {
        return (e instanceof CONST && ((CONST) e).value == (short) ((CONST) e).value);
    }

    public Temp visit(MOVE s, LinkedList<Instr> insns) {
        // MOVE(MEM, Exp)
        if (s.dst instanceof MEM) {
            MEM mem = (MEM) s.dst;
            // MOVE(MEM(Exp, CONST16), Exp)
            if (CONST16(mem.offset)) {
                String offset = Integer.toString(mem.offset.value);
                if (mem.exp instanceof NAME) {
                    NAME exp = (NAME) mem.exp;
                    Temp src = s.src.accept(this, insns);
                    Temp t = new Temp();
                    String loc = exp.label + "+" + offset;
                    insns.add(OPER("lis `d0," + frame.ha16(loc), T(t), T()));
                    insns.add(OPER("stw `s0," + frame.lo16(loc) + "(`s1)", T(),
                                   T(src, t)));
                    return null;
                } else {
                    Temp exp =
                        mem.exp instanceof TEMP ? ((TEMP) mem.exp).temp
                            : mem.exp.accept(this, insns);
                    Temp src = s.src.accept(this, insns);
                    if (exp == frame.FP) {
                        exp = frame.SP;
                        offset += "+__framesize";
                    }
                    insns.add(OPER("stw `s0," + offset + "(`s1)", T(), T(src,
                                                                         exp)));
                    return null;
                }
            }
            // MOVE(MEM(Exp, CONST), Exp)
            Temp exp = mem.exp.accept(this, insns);
            Temp off = mem.offset.accept(this, insns);
            Temp src = s.src.accept(this, insns);
            insns.add(OPER("add `d0,`s0,`s1", T(exp), T(exp, off)));
            insns.add(OPER("stw `s0,0(`s1)", T(), T(src, exp)));
            return null;
        }

        Temp dst = s.dst.accept(this, insns);
        Temp src = s.src.accept(this, insns);
        if (src != dst)
            insns.add(MOVE(dst, src));
        return null;
    }

    public Temp visit(ESTM s, LinkedList<Instr> insns) {
        return s.exp.accept(this, insns);
    }

    public Temp visit(JUMP s, LinkedList<Instr> insns) {
        if (s.exp instanceof NAME) {
            // JUMP(Tree.NAME, List<Label>)
            insns.add(OPER("b `j0", T(), T(), s.targets));
            return null;
        }
        // This is the general case
        // JUMP(Exp, List<Label>)
        Temp s0 = s.exp.accept(this, insns);
        insns.add(OPER("mtctr `s0", T(), T(s0)));
        insns.add(OPER("bctr", T(), T(), s.targets));
        return null;
    }

    public Temp visit(CJUMP s, LinkedList<Instr> insns) {
        String op;
        switch (s.op) {
        case BEQ:
            op = "beq";
            break;
        case BNE:
            op = "bne";
            break;
        case BLT:
            op = "blt";
            break;
        case BGT:
            op = "bgt";
            break;
        case BLE:
            op = "ble";
            break;
        case BGE:
            op = "bge";
            break;
        default:
            throw new Error();
        }
        // CJUMP(Exp, CONST, Label, Label)
        if (CONST16(s.left) && !CONST16(s.right)) {
            s.swap().accept(this, insns);
            return null;
        }
        if (CONST16(s.right)) {
            CONST c = (CONST) s.right;
            Temp s0 = s.left.accept(this, insns);
            insns.add(OPER("cmpwi `s0," + c.value, T(), T(s0)));
            insns.add(OPER(op + " `j0", T(), T(), s.iftrue, s.iffalse));
            return null;
        }
        // CJUMP(Exp, Exp, Label, Label)
        Temp s0 = s.left.accept(this, insns);
        Temp s1 = s.right.accept(this, insns);
        insns.add(OPER("cmpw `s0,`s1", T(), T(s0, s1)));
        insns.add(OPER(op + " `j0", T(), T(), s.iftrue, s.iffalse));
        return null;
    }

    public Temp visit(LABEL l, LinkedList<Instr> insns) {
        insns.add(new Instr.LABEL(l.label.toString() + ":", l.label));
        return null;
    }

    public Temp visit(CONST e, LinkedList<Instr> insns) {
        Temp d0 = new Temp();
        if (CONST16(e)) {
            insns.add(OPER("li `d0," + e.value, T(d0), T()));
        } else {
            String value = Integer.toString(e.value);
            insns.add(OPER("lis `d0," + frame.hi16(value), T(d0), T()));
            insns.add(OPER("ori `d0,`s0," + frame.lo16(value), T(d0), T(d0)));
        }
        return d0;
    }

    public Temp visit(NAME e, LinkedList<Instr> insns) {
        Temp t = new Temp();
        insns.add(OPER("lis `d0," + frame.ha16(e.label.toString()), T(t), T()));
        insns.add(OPER("la `d0," + frame.lo16(e.label.toString()) + "(`s0)",
                       T(t), T(t)));
        return t;
    }

    public Temp visit(TEMP e, LinkedList<Instr> insns) {
        Temp t = e.temp;
        if (t == frame.FP) {
            t = new Temp();
            insns.add(OPER("addi `d0,`s0,__framesize", T(t), T(frame.SP)));
        }
        return t;
    }

    private static int shift(int i) {
        int shift = 0;
        if ((i >= 2) && ((i & (i - 1)) == 0)) {
            while (i > 1) {
                shift += 1;
                i >>= 1;
            }
        }
        return shift;
    }

    public Temp visit(BINOP b, LinkedList<Instr> insns) {
        String op;
        Exp l = b.left;
        Exp r = b.right;
        switch (b.op) {
        case ADD: {
            if (CONST16(l) && !CONST16(r)) {
                Exp t = l;
                l = r;
                r = t;
            }
            if (CONST16(r)) {
                CONST c = (CONST) r;
                String offset = Integer.toString(c.value);
                Temp d0 = new Temp();
                Temp s0 =
                    l instanceof TEMP ? ((TEMP) l).temp : l.accept(this, insns);
                if (s0 == frame.FP) {
                    s0 = frame.SP;
                    offset += "+__framesize";
                }
                insns.add(OPER("addi `d0,`s0," + offset, T(d0), T(s0)));
                return d0;
            }
            op = "add";
            break;
        }
        case AND: {
            if (CONST16(l) && !CONST16(r)) {
                Exp t = l;
                l = r;
                r = t;
            }
            op = "and";
            break;
        }
        case DIV: {
            if (r instanceof CONST) {
                CONST c = (CONST) r;
                int shift = shift(c.value);
                if (shift != 0) {
                    Temp d0 = new Temp();
                    Temp s0 = l.accept(this, insns);
                    insns.add(OPER("srawi `d0,`s0," + shift, T(d0), T(s0)));
                    return d0;
                }
            }
            Temp d0 = new Temp();
            Temp s0 = l.accept(this, insns);
            Temp s1 = r.accept(this, insns);
            insns.add(OPER("divw `d0,`s0,`s1", T(d0), T(s0, s1)));
            return d0;
        }
        case DIVU: {
            if (r instanceof CONST) {
                CONST c = (CONST) r;
                int shift = shift(c.value);
                if (shift != 0) {
                    Temp d0 = new Temp();
                    Temp s0 = l.accept(this, insns);
                    insns.add(OPER("srwi `d0,`s0," + shift, T(d0), T(s0)));
                    return d0;
                }
            }
            Temp d0 = new Temp();
            Temp s0 = l.accept(this, insns);
            Temp s1 = r.accept(this, insns);
            insns.add(OPER("divwu `d0,`s0,`s1", T(d0), T(s0, s1)));
            return d0;
        }
        case MUL: {
            if (r instanceof CONST) {
                CONST c = (CONST) r;
                int shift = shift(c.value);
                if (shift != 0) {
                    Temp d0 = new Temp();
                    Temp s0 = l.accept(this, insns);
                    insns.add(OPER("slwi `d0,`s0," + shift, T(d0), T(s0)));
                    return d0;
                }
            }
            if (l instanceof CONST) {
                CONST c = (CONST) l;
                int shift = shift(c.value);
                if (shift != 0) {
                    Temp d0 = new Temp();
                    Temp s0 = r.accept(this, insns);
                    insns.add(OPER("slwi `d0,`s0," + shift, T(d0), T(s0)));
                    return d0;
                }
            }
            if (CONST16(l) && !CONST16(r)) {
                Exp t = l;
                l = r;
                r = t;
            }
            if (CONST16(r)) {
                CONST c = (CONST) r;
                String offset = Integer.toString(c.value);
                Temp d0 = new Temp();
                Temp s0 = l.accept(this, insns);
                insns.add(OPER("mulli `d0,`s0," + offset, T(d0), T(s0)));
                return d0;
            }
            Temp d0 = new Temp();
            Temp s0 = l.accept(this, insns);
            Temp s1 = r.accept(this, insns);
            insns.add(OPER("mullw `d0,`s0,`s1", T(d0), T(s0, s1)));
            return d0;
        }
        case OR:
            if (CONST16(l) && !CONST16(r)) {
                Exp t = l;
                l = r;
                r = t;
            }
            op = "or";
            break;
        case SLL:
            op = "slw";
            break;
        case SRA:
            op = "sraw";
            break;
        case SRL:
            op = "srw";
            break;
        case SUB:
            op = "sub";
            break;
        case XOR:
            if (CONST16(l) && !CONST16(r)) {
                Exp t = l;
                l = r;
                r = t;
            }
            op = "xor";
            break;
        default:
            throw new Error();
        }
        if (CONST16(r)) {
            CONST c = (CONST) r;
            String offset = Integer.toString(c.value);
            Temp d0 = new Temp();
            Temp s0 = l.accept(this, insns);
            insns.add(OPER(op + "i `d0,`s0," + offset, T(d0), T(s0)));
            return d0;
        }
        Temp d0 = new Temp();
        Temp s0 = l.accept(this, insns);
        Temp s1 = r.accept(this, insns);
        insns.add(OPER(op + " `d0,`s0,`s1", T(d0), T(s0, s1)));
        return d0;
    }

    public Temp visit(MEM mem, LinkedList<Instr> insns) {
        Temp dst = new Temp();
        // MEM(Exp, CONST16)
        if (CONST16(mem.offset)) {
            String offset = Integer.toString(mem.offset.value);
            if (mem.exp instanceof NAME) {
                NAME exp = (NAME) mem.exp;
                String loc = exp.label + "+" + offset;
                insns.add(OPER("lis `d0," + frame.ha16(loc), T(dst), T()));
                insns.add(OPER("lwz `d0," + frame.lo16(loc) + "(`s0)", T(dst),
                               T(dst)));
                return dst;
            } else {
                Temp exp =
                    mem.exp instanceof TEMP ? ((TEMP) mem.exp).temp
                        : mem.exp.accept(this, insns);
                if (exp == frame.FP) {
                    exp = frame.SP;
                    offset += "+__framesize";
                }
                insns.add(OPER("lwz `d0," + offset + "(`s0)", T(dst), T(exp)));
                return dst;
            }
        }
        // MEM(Exp, CONST)
        Temp exp = mem.exp.accept(this, insns);
        Temp off = mem.offset.accept(this, insns);
        insns.add(OPER("add `d0,`s0,`s1", T(exp), T(exp, off)));
        insns.add(OPER("lwz `d0,0(`s0)", T(dst), T(exp)));
        return dst;
    }

    public Temp visit(CALL s, LinkedList<Instr> insns) {
        Temp s0 = s.func instanceof NAME ? null : s.func.accept(this, insns);
        Temp[] uses =
            new Temp[(s.args.length < frame.argRegs.length) ? s.args.length
                : frame.argRegs.length];
        int use = 0;
        for (int i = 0; i < s.args.length; i++) {
            if (i < frame.argRegs.length) {
                Temp d0 = frame.argRegs[i];
                Temp arg = s.args[i].accept(this, insns);
                if (arg != d0)
                    insns.add(MOVE(d0, arg));
                uses[use++] = d0;
            } else {
                Temp arg = s.args[i].accept(this, insns);
                insns.add(OPER("stw `s0,"
                    + (i * Frame.wordSize + Frame.linkageSize) + "(`s1)", T(),
                               T(arg, frame.SP)));
            }
        }
        if (s.func instanceof NAME) {
            NAME n = (NAME) s.func;
            insns.add(OPER("bl " + n.label, frame.callDefs, uses));
        } else {
            insns.add(OPER("mtctr `s0", T(), T(s0)));
            insns.add(OPER("bctrl", frame.callDefs, uses));
        }
        return frame.A0;
    }

    // Canonical trees shouldn't have these so throw an error
    public Temp visit(SEQ n, LinkedList<Instr> insns) {
        throw new Error();
    }

    public Temp visit(ESEQ n, LinkedList<Instr> insns) {
        throw new Error();
    }
}
